﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using System.Windows.Controls;
using System.Windows.Documents;
using XPathPlugin.XPathExpressionUtils;

namespace XPathPlugin.XPathTreeUtils
{
    class XPathTreeTransformer
    {
        #region Variables

        private bool _initialized = false;

        private XPathNode _xpathTree;        
        private RichTextBox _expressionBox;

        private static readonly string _UnionDelimiter = " | ";
        private static readonly string _AndDelimiter = " and ";
        private static readonly string _OrDelimiter = " or ";
        private static readonly XPathExpressionParser _parser = new XPathExpressionParser();

        private List<XPathExpression> _expressionList;
        private Dictionary<XPathExpression, List<TextPointer[]>> _expressionTextMapping;

        private TextPointer _textStartPoint;
        private FlowDocument _document;

        #endregion

        public XPathTreeTransformer(XPathTree tree, RichTextBox expressionBox)
            : this(tree as XPathNode, expressionBox)
        {
        }

        public XPathTreeTransformer(XPathNode node, RichTextBox expressionBox)
        {
            _xpathTree = node;
            _expressionBox = expressionBox;            

            _expressionList = new List<XPathExpression>();
            _expressionTextMapping = new Dictionary<XPathExpression, List<TextPointer[]>>();

            try
            {
                Initialize();

                TransformTree();
            }
            catch (Exception) { throw new PluginException("Transformer exception"); }
        }

        /// <summary>
        /// Gets list of XPathExpression
        /// </summary>
        public List<XPathExpression> ExpressionList
        {
            get { return _expressionList; }
        }

        /// <summary>
        /// Gets expression maping to their TextPointer in expression box
        /// </summary>
        public Dictionary<XPathExpression, List<TextPointer[]>> ExpressionTextMap
        {
            get { return _expressionTextMapping; }
        }

        /// <summary>
        /// Initialize Transformer
        /// </summary>
        private void Initialize()
        {
            if (_xpathTree == null || _expressionBox == null)
            {
                _initialized = false;
                return;
            }

            _document = _expressionBox.Document;
            
            _textStartPoint = _document.Blocks.FirstBlock.ContentStart;

            // Find first text occurance
            while (true)
            {
                // If text pointer points to text value
                if (_textStartPoint.Parent is Run)
                    break;

                // Get next text pointer
                _textStartPoint = _textStartPoint.GetNextContextPosition(LogicalDirection.Forward);
            }


            _initialized = true;
        }        
        
        /// <summary>
        /// Transforms tree to expression list and expression maping to their text pointers
        /// </summary>
        private void TransformTree()
        {
            if (!_initialized)
                return;

            IEnumerator<XPathNode> enumerator = _xpathTree.GetDepthLastEnumerator();
            XPathExpression expression;
            List<TextPointer[]> pointerList;

            // Walk in tree
            while (enumerator.MoveNext())
            {
                XPathNode node = enumerator.Current;
                expression = XPathExpression.Compile(GetExpression(node));
                pointerList = GetExpressionTextMapping(node);
                
                _expressionList.Add(expression);
                _expressionTextMapping.Add(expression, pointerList);
            }

            // Control expressions
            ControlExpressionList();
        }                

        /// <summary>
        /// Check and remove same expressions
        /// </summary>
        private void ControlExpressionList()
        {
            XPathExpression checkExpression = null;
            XPathExpression expression;
            for (int index = 0; index < _expressionList.Count; index++)                
            {
                expression = _expressionList[index];
                if (checkExpression != null && 
                        (checkExpression.Expression.Equals(expression.Expression) || 
                            (expression.Expression.StartsWith("(") && expression.Expression.EndsWith(")") && index < _expressionList.Count - 1)) )
                {
                    _expressionList.Remove(expression);
                    _expressionTextMapping.Remove(expression);
                    --index;
                }
                checkExpression = expression;
            }
        }

        /// <summary>
        /// Returns built expression for XPathNode node
        /// </summary>
        /// <param name="node">node for to build string</param>
        /// <returns>string</returns>
        private string GetExpression(XPathNode node)
        {
            if (node == null)
                return "";

            else if (node.Parent == null)
                return node.Value.Value;

            string value = "";
            string leftSiblingsValue;
            bool firstIteration = true;

            while (node.Parent != null)
            {
                leftSiblingsValue = "";
                foreach (XPathNode xNode in node.Parent.Children)
                {
                    if (xNode.Value.Type == XPathValue.NodeType.FilterInside && xNode == node.Parent.Children[0])
                        leftSiblingsValue += "[";

                    if (xNode == node)                                        
                        break;                                       

                    leftSiblingsValue += xNode.Value.Value;
                    switch(xNode.Value.TypeOfJoin) 
                    {
                        case XPathValue.JoinType.Union:
                            leftSiblingsValue += _UnionDelimiter;
                            break;

                        case XPathValue.JoinType.And:
                            leftSiblingsValue += _AndDelimiter;
                            break;

                        case XPathValue.JoinType.Or:
                            leftSiblingsValue += _OrDelimiter;
                            break;
                    }
                }

                if (firstIteration)
                    leftSiblingsValue += node.Value.Value + (node.Value.Type == XPathValue.NodeType.FilterInside ? "]" : "");
                
                value = leftSiblingsValue + value;
                firstIteration = false;
                node = node.Parent;
            }
            
            return value;
        }

        /// <summary>
        /// Return expression mapping in expression box
        /// </summary>
        /// <param name="node">node to be mapped</param>
        /// <returns>List{TextPointer[]}</returns>
        private List<TextPointer[]> GetExpressionTextMapping(XPathNode node)
        {
            if (node == null)
                return null;

            List<TextPointer[]> pointerList = new List<TextPointer[]>();
            TextPointer startPointer = _textStartPoint;
            TextPointer endPointer = _textStartPoint.GetPositionAtOffset(_textStartPoint.GetTextRunLength(LogicalDirection.Forward));
            string textInRun;

            if (node.Parent == null)
            {

                pointerList.Add(new TextPointer[] { _document.ContentStart, _document.ContentEnd });
                return pointerList;
            }

            List<XPathNode> nodeList = new List<XPathNode>();
                        
            do
            {
                if (node.Parent != null)
                {
                    int nodeIndex = node.Parent.Children.IndexOf(node);
                    for (int index = nodeIndex; index < node.Parent.Children.Count; index++)
                        nodeList.Add(node.Parent.Children[index]);                    
                }
                else
                    nodeList.Add(node);
                                
                node = node.Parent;
            }
            while (node != null);

            nodeList.Reverse();
            XPathNode lastNode = nodeList[nodeList.Count - 1];
            string nodeValue;

            foreach(XPathNode xNode in nodeList)
            {                            
                textInRun = endPointer.GetTextInRun(LogicalDirection.Backward);
                nodeValue = xNode.Value.Value;
                int lastIndexOf = textInRun.LastIndexOf(nodeValue, StringComparison.CurrentCultureIgnoreCase);

                if (IsDirectDescendant(lastNode, xNode) && nodeValue.StartsWith("(") && nodeValue.EndsWith(")"))
                {
                    TextPointer rightParensStart = endPointer.GetPositionAtOffset(lastIndexOf + nodeValue.Length - textInRun.Length - 1, LogicalDirection.Backward);
                    TextPointer rightParensEnd = rightParensStart.GetPositionAtOffset(1, LogicalDirection.Forward);
                    pointerList.Add(new TextPointer[] { rightParensStart, rightParensEnd });
                    continue;
                }

                if (!IsDirectDescendant(lastNode, xNode))
                {
                    if (lastIndexOf > 0 && xNode != lastNode)
                        endPointer = endPointer.GetPositionAtOffset(lastIndexOf - textInRun.Length, LogicalDirection.Backward);

                    else if (lastIndexOf >= 0)
                        endPointer = endPointer.GetPositionAtOffset(lastIndexOf + nodeValue.Length - textInRun.Length, LogicalDirection.Backward);
                }
            }

            pointerList.Add(new TextPointer[] { startPointer, endPointer });

            // If node type is FilterInside -> look for right square parens index
            if (lastNode.Value.Type == XPathValue.NodeType.FilterInside)
            {
                int rightSquareParensIndex = _parser.IndexOf(']', endPointer.GetTextInRun(LogicalDirection.Forward).ToCharArray(), 1);
                TextPointer rightSquareStart = endPointer.GetPositionAtOffset(rightSquareParensIndex, LogicalDirection.Forward);
                TextPointer rightSquareEnd = endPointer.GetPositionAtOffset(rightSquareParensIndex + 1, LogicalDirection.Forward);
                pointerList.Add(new TextPointer[] {rightSquareStart, rightSquareEnd});
            }

            return pointerList;
        }

        /// <summary>
        /// Returns whether node descendant is diract descendat if node
        /// </summary>
        /// <param name="descendant">descendant node</param>
        /// <param name="node">node to check</param>
        /// <returns>bool</returns>
        private bool IsDirectDescendant(XPathNode descendant, XPathNode node)
        {
            if (descendant == null || node == null)
                return false;

            if (node.Parent == null)
                return true;

            if (descendant == node)
                return false;

            while (descendant.Parent != null)
            {
                if (descendant == node)
                    return true;
                
                descendant = descendant.Parent;
            }

            return false;
        }

        /// <summary>
        /// Gets string representing expression list
        /// </summary>
        /// <returns>string</returns>
        public override string ToString()
        {
            string value = "";
            foreach (XPathExpression expression in _expressionList)
            {
                value += expression.Expression + System.Environment.NewLine;
            }
            return value;
        }
    }
}
